﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class FinPlusEquate: FinPlusComponent
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "ModelTest", "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return _model; } }
        public override string ModelTest { get { return Model.ToString(); } }
        public override bool IsValid { get; set; }

        private enum Params { LevelUpdate };
        private enum DockControls { FinPlusDataConn, FinPlusChildService, Default }
        private readonly IDictionary<string, IFinPlusComponent> _conns;
        private readonly IDictionary<string, IFinPlusFunction> _index;
        private readonly IDictionary<string, string> _fields, _links, _expressions, _totals;
        private readonly IList<string> _keyLabels;
        private readonly IFinPlusFunctions _model;
        private int _id, _numConn; 
        private bool _hasInitialised;

        //construct
        public FinPlusEquate(string numConn, string keyLables, string fields, string links, string expressions, string totals)
        {
            _numConn = int.Parse(numConn);
            _conns = new Dictionary<string, IFinPlusComponent>();
            _index = new Dictionary<string, IFinPlusFunction>();
            _model = new Functions();
            _keyLabels = u.Split(keyLables, ',').ToList();

            _fields = u.ToDictionary(fields, false, ';','.');
            _links = u.ToDictionary(links, false);
            _expressions = u.ToDictionary(expressions, true);
            _totals = u.ToDictionary(totals, false);
        }

        //common control interface
        public override void Dock(object component, string dock) 
        {
            try
            {
                switch (u.EnumTryParse<DockControls>(component.GetType().Name, DockControls.Default))
                {
                    case DockControls.FinPlusChildService:
                    case DockControls.FinPlusDataConn:
                    case DockControls.Default: AddConnection((IFinPlusComponent)component, dock); break;
                }
                Initialise();
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Initialise()
        {
            if (_hasInitialised || _conns.Count() < _numConn) return;

            foreach (var kvp in _conns)
                AddFields(kvp.Value.Model, kvp.Key);

            foreach (var kvp in _conns)
                AddLinks(kvp.Value.Model, kvp.Key);

            AddExpressions(_model);

            AddTotals(_model);

            IsValid = _hasInitialised = true;
        }

        public override void CommandRouter(IFinPlusFunction func) { }

        public override void Dispose() 
        {
            foreach (var conn in _conns.Values)
                conn.Dispose();
        }

        //private
        private void AddConnection(IFinPlusComponent conn, string dock)
        {
            _conns[dock] = conn;
        }

        private void AddFields(IFinPlusFunctions funcs, string name)
        {
            try
            {
                if (_fields == null) return;

                foreach (var f in _fields.Where(s => s.Value.Equals(name)))
                    foreach (var func in funcs.Values)
                        AddField(func, f.Key);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddField(IFinPlusFunction func, string name)
        {
            try
            {
                var val = func.Get(name);
                if (val == null) 
                    return;

                var f = GetFunction(func, name);
                val.Index = -1;
                f.TryAdd(val); 
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddLinks(IFinPlusFunctions funcs, string name)
        {
            try
            {
                if (_links == null) return;

                foreach (var f in _links.Where(s => s.Key.Split('.')[0].Equals(name)))
                    foreach (var func in funcs.Values)
                        AddLink(func, f.Value, f.Key);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddLink(IFinPlusFunction func, string left, string right)
        {
            try
            {
                string[] l = left.Split('.'), r = right.Split('.');
                var label = func[l[1]].ToString();
                foreach (var model in _model.ToList(l[0], label))
                    model.TryAdd(func.Get<double>(r[1], 0.0));
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddExpressions(IFinPlusFunctions funcs)
        {
            try
            {
                if (_expressions == null) return;

                foreach (var f in _expressions)
                    foreach (var func in funcs.Values)
                        AddExpression(func, f.Key, f.Value);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddExpression(IFinPlusFunction func, string name, string expression)
        {
            try
            {
                var item = GetFunction(func, name).Add(name, 0.0);
                item.Evaluate(func, expression);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddTotals(IFinPlusFunctions funcs)
        {
            try
            {
                if (_totals == null) return;
                
                foreach (var total in _totals)
                    AddTotal(funcs, total.Value, total.Key);
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void AddTotal(IFinPlusFunctions funcs, string left, string right)
        {
            try
            {
                string[] l = left.Split('.'), r = right.Split('.');
                foreach (var func in funcs.Values.Where(f => f[l[0]].Equals(l[1])))
                {
                    var v = func.ToList(r[1], false);
                    if (v.Count() < 1) continue;
                    var item = v.First();
                    var link = func.Where(i => i.Name.Equals(r[0])).First().Value.ToString();
                    var list = new List<IFinPlusItem>();
                    foreach (var func2 in funcs.Values)
                        if (func2[r[0]].Equals(link) && !func2.Equals(func))
                            list.AddRange(func2.ToList(r[1], false));
                    item.Total(list);
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private IFinPlusFunction GetFunction(IFinPlusFunction func, string name)
        {
            var key = string.Empty;
            foreach (var keyLabel in _keyLabels)
                key += func[keyLabel] + "|";

            key = key.TrimEnd('|');
                    
            if (_index.ContainsKey(key)) 
                return _index[key];

            return AddFunction(func, key);
        }

        private IFinPlusFunction AddFunction(IFinPlusFunction func, string key)
        {
            _id++;
            IFinPlusFunction f;
            _index[key] = f = new Function(Id, _id.ToString(), User, _id);
            foreach (var i in func)
                if (_keyLabels.Contains(i.Name))
                    f.TryAdd(i);

            _model.Add(f);

            return f;
        }
    }
}
